package ru.papergames.web.blines.battle.network;

import ru.common.network.clientcore.MMOClient;
import ru.common.network.clientcore.MMOConnection;
import ru.common.network.clientcore.SendablePacket;
import ru.papergames.web.blines.battle.BattleServerConnectionsHolderThread.BattleServerConnectionHolder;
import ru.papergames.web.blines.battle.network.clientpackets.WebServerClientPacket;
import ru.papergames.web.blines.battle.network.serverpackets.CompositeBuffer;

import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Represents a client connected to Game Battle-Server
 *
 * @author KenM
 */
public final class ClientWebServer extends MMOClient<MMOConnection<ClientWebServer>> {
    protected static final Logger _log = Logger.getLogger(ClientWebServer.class.getName());

    /**
     * CONNECTED	- client has just connected
     * AUTHED		- client has authed but doesnt has character attached to it yet
     * IN_GAME		- client has selected a char and is in game
     *
     * @author KenM
     */
    public static enum ClientWebServerState {
        CONNECTED, AUTHED, IN_GAME
    }

    public ClientWebServerState state;

    // Info
    private String accountName;
//    private SessionKey _sessionId;
//    private OsidePcInstance activeChar;
    @Deprecated
    private ReentrantLock activeCharLock = new ReentrantLock();

    private boolean isAuthedGG;
    private long connectionStartTime;

    // Task
    protected ScheduledFuture<?> cleanupTask = null;

    // Crypt
    private GameCrypt crypt;

    // Flood protection
    public byte packetsSentInSec = 0;
    public int packetsSentStartTick = 0;

    private boolean detached = false;

    private boolean protocol;

    private BattleServerConnectionHolder connectionHolder;

    private Map<Integer, CompositeBuffer> compositePackets = new HashMap<Integer, CompositeBuffer>();

    public ClientWebServer(MMOConnection<ClientWebServer> con) {
        super(con);
        state = ClientWebServerState.CONNECTED;
        connectionStartTime = System.currentTimeMillis();
        crypt = new GameCrypt();
    }

    public byte[] enableCrypt() {
        byte[] key = BlowFishKeygen.getRandomKey();
        crypt.setKey(key);
        return key;
    }

    public ClientWebServerState getState() {
        return state;
    }

    public void setState(ClientWebServerState pState) {
        state = pState;
    }

    public long getConnectionStartTime() {
        return connectionStartTime;
    }

    @Override
    public boolean decrypt(ByteBuffer buf, int size) {
        crypt.decrypt(buf.array(), buf.position(), size);
        return true;
    }

    @Override
    public boolean encrypt(final ByteBuffer buf, final int size) {
        crypt.encrypt(buf.array(), buf.position(), size);
        buf.position(buf.position() + size);
        return true;
    }

    public ReentrantLock getActiveCharLock() {
        return activeCharLock;
    }

    public void setGameGuardOk(boolean val) {
        isAuthedGG = val;
    }

    public boolean isAuthedGG() {
        return isAuthedGG;
    }

    public void setAccountName(String pAccountName) {
        accountName = pAccountName;
    }

    public String getAccountName() {
        return accountName;
    }

    public void setConnectionHolder(BattleServerConnectionHolder connectionHolder) {
        this.connectionHolder = connectionHolder;
    }

    //    public void setSessionId(SessionKey sk) {
//        _sessionId = sk;
//    }
//
//    public SessionKey getSessionId() {
//        return _sessionId;
//    }
//
//    public void sendPacket(WebServerClientPacket gsp) {
    public void sendPacket(SendablePacket<ClientWebServer> gsp) {
        if (detached) return;

        // Packets from invisible chars sends only to GMs
//        if (gsp.isInvisible() && getActiveChar() != null && !getActiveChar().isGM())
//            return;

        getConnection().sendPacket(gsp);
        //gsp.runImpl();
    }

    public boolean isDetached() {
        return detached;
    }

    public void isDetached(boolean b) {
        detached = b;
    }

    public void close(WebServerClientPacket gsp) {
        getConnection().close(gsp);
    }

    public BattleServerConnectionHolder getConnectionHolder() {
        return connectionHolder;
    }

    @Override
    protected void onForcedDisconnection() {
        _log.info("[ClientWebServer]Client " + toString() + " disconnected abnormally.");
        //connectionHolder.onConnectionClose();
    }

    @Override
    protected void onDisconnection() {
        _log.info("[ClientWebServer]Client " + toString() + " disconnected.");
        connectionHolder.onConnectionClose();
        // no long running tasks here, do it async
/*
        try {
            ThreadPoolManager.getInstance().executeTask(new DisconnectTask());
        }
        catch (RejectedExecutionException e) {
            // server is closing
        }
*/
    }

    public Map<Integer, CompositeBuffer> getCompositePackets() {
        return compositePackets;
    }

    public void closeSocket() {
        try {
            getConnection().close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public void closeNow() {
        super.getConnection().close(null);
        cleanMe(true);
    }

    /**
     * Produces the best possible string representation of this client.
     */
    @Override
    public String toString() {
        try {
            InetAddress address = getConnection().getInetAddress();
            switch (getState()) {
                case CONNECTED:
                    return "[CONNECTED, IP: " + (address == null ? "disconnected" : address.getHostAddress()) + "]";
                case AUTHED:
                    return "[AUTHED, Account: " + getAccountName() + " - IP: " + (address == null ? "disconnected" : address.getHostAddress()) + "]";
                case IN_GAME:
                    return "[IN_GAME, Character:  - Account: " + getAccountName() + " - IP: " + (address == null ? "disconnected" : address.getHostAddress()) + "]";
                default:
                    throw new IllegalStateException("Missing state on switch");
            }
        }
        catch (NullPointerException e) {
            return "[Character read failed due to disconnect]";
        }
    }

    class DisconnectTask implements Runnable {

        /**
         * @see Runnable#run()
         */
        public void run() {
            boolean fast = true;

            try {
                isDetached(true);
//                OsidePcInstance player = OsideClientWebServer.this.getActiveChar();
//                if (player.isInCombat() || player.isLocked()) {
//                    fast = false;
//                }
//                cleanMe(fast);

//                OsidePcInstance player = OsideClientWebServer.this.getActiveChar();
//                if (player != null)  // this should only happen on connection loss
//                {
//                    if (player.isLocked()) {
//                        _log.log(Level.WARNING, "Player " + player.getName() + " still performing subclass actions during disconnect.");
//                    }
//
//                    // to prevent call cleanMe() again
//                    isDetached(false);
//                    // notify the world about our disconnect
//                    player.deleteMe();
//
//                }
//                OsideClientWebServer.this.setActiveChar(null);
            }
            catch (Exception e1) {
                _log.log(Level.WARNING, "Error while disconnecting client.", e1);
            }
//            finally {
//                LoginServerThread.getInstance().sendLogout(OsideClientWebServer.this.getAccountName());
//            }
        }
    }

    public void cleanMe(boolean fast) {
        try {
            synchronized (this) {
                if (cleanupTask == null) {
                    //cleanupTask = ThreadPoolManager.getInstance().scheduleGeneral(new CleanupTask(), fast ? 5 : 15000L);
                }
            }
        }
        catch (Exception e1) {
            _log.log(Level.WARNING, "Error during cleanup.", e1);
        }
    }

    class CleanupTask implements Runnable {
        /**
         * @see Runnable#run()
         */
        public void run() {
            try {
                // TODO: move this part of code in @see DisconnectTask#run

//                OsidePcInstance player = OsideClientWebServer.this.getActiveChar();
//                if (player != null)  // this should only happen on connection loss
//                {
//                    if (player.isLocked()) {
//                        _log.log(Level.WARNING, "Player " + player.getName() + " still performing subclass actions during disconnect.");
//                    }
//
//                    // to prevent call cleanMe() again
//                    isDetached(false);
//                    // notify the world about our disconnect
//                    player.deleteMe();
//
//                }
//                OsideClientWebServer.this.setActiveChar(null);
            }
            catch (Exception e1) {
                _log.log(Level.WARNING, "Error while cleanup client.", e1);
            }
//            finally {
//                LoginServerThread.getInstance().sendLogout(OsideClientWebServer.this.getAccountName());
//            }
        }
    }

    public boolean isProtocolOk() {
        return protocol;
    }

    public void setProtocolOk(boolean b) {
        protocol = b;
	}
}
